/**
 * 定时器管理工具
 * 
 * 本模块提供了一个跨环境（浏览器和Node.js）的定时器管理解决方案，
 * 主要功能包括：
 * 1. 自动适配不同环境的setTimeout和clearTimeout实现
 * 2. 集中管理活跃的定时器，防止内存泄漏
 * 3. 提供命名定时器功能，便于跟踪和管理
 * 4. 支持批量清除定时器
 */

/**
 * 兼容浏览器和Node.js的定时器ID类型
 * - 浏览器环境：通常是number类型
 * - Node.js环境：通常是Timeout对象类型
 */
export type TimerId = number | NodeJS.Timeout;

/**
 * 定时器引用对象接口
 * 用于存储命名定时器的引用，方便统一管理
 */
export interface TimeoutRefs {
  /**
   * 定时器的唯一标识作为键
   * 值为对应的定时器ID或undefined（表示已清除）
   */
  [key: string]: TimerId | undefined;
}

/**
 * 环境检测标志
 * 通过检查全局window对象和document对象是否存在来判断当前环境是否为浏览器
 */
const isBrowser = typeof window !== 'undefined' && typeof window.document !== 'undefined';

/**
 * 获取当前环境的setTimeout函数
 * 
 * 该函数会根据当前运行环境自动返回对应的setTimeout实现：
 * - 浏览器环境：返回window.setTimeout的绑定版本
 * - Node.js环境：返回全局setTimeout函数
 * 
 * @returns 适配当前环境的setTimeout函数
 */
const getGlobalSetTimeout = (): ((callback: () => void, timeout: number) => TimerId) => {
  if (isBrowser) {
    // 浏览器环境：使用window.setTimeout并进行类型转换
    return window.setTimeout.bind(window) as unknown as (callback: () => void, timeout: number) => TimerId;
  } else {
    // Node.js环境：使用全局setTimeout并进行类型转换
    return setTimeout as unknown as (callback: () => void, timeout: number) => TimerId;
  }
};

/**
 * 获取当前环境的clearTimeout函数
 * 
 * 该函数会根据当前运行环境自动返回对应的clearTimeout实现：
 * - 浏览器环境：返回window.clearTimeout的绑定版本
 * - Node.js环境：返回全局clearTimeout函数
 * 
 * @returns 适配当前环境的clearTimeout函数
 */
const getGlobalClearTimeout = (): ((timerId: TimerId) => void) => {
  if (isBrowser) {
    // 浏览器环境：使用window.clearTimeout并进行类型转换
    return window.clearTimeout.bind(window) as unknown as (timerId: TimerId) => void;
  } else {
    // Node.js环境：使用全局clearTimeout并进行类型转换
    return clearTimeout as unknown as (timerId: TimerId) => void;
  }
};

/**
 * 定时器管理类
 * 
 * 提供了定时器的创建、管理和清除功能，同时处理跨环境兼容性问题。
 * 主要特性：
 * - 自动适配浏览器和Node.js环境
 * - 跟踪所有活跃的定时器，防止内存泄漏
 * - 提供命名定时器管理功能
 */
export class TimerManager {
  /**
   * 存储所有活跃的定时器ID（支持所有类型的ID，兼容浏览器和Node.js环境）
   * 用于批量清除所有定时器，防止内存泄漏
   */
  private __activeTimers = new Set<TimerId>();

  /**
   * 存储所有命名定时器的引用
   * 实例内部自动管理，不需要用户传递
   */
  private __namedTimeouts: TimeoutRefs = {};
  /**
   * 当前环境的setTimeout实现函数
   */
  private __setTimeout: (callback: () => void, timeout: number) => TimerId;

  /**
   * 当前环境的clearTimeout实现函数
   */
  private __clearTimeout: (timerId: TimerId) => void;

  /**
   * 构造函数
   * 初始化环境适配的定时器函数
   */
  constructor() {
    // 初始化时获取当前环境的定时器函数
    this.__setTimeout = getGlobalSetTimeout();
    this.__clearTimeout = getGlobalClearTimeout();
  }

  /**
   * 添加一个计时器
   * 
   * 封装了原生setTimeout函数，增加了自动跟踪和清理功能。
   * 定时器触发后会自动从活跃定时器集合中移除。
   * 
   * @param timeout 延迟时间（毫秒）
   * @param callback 延迟后执行的回调函数
   * @returns 计时器ID（统一返回number类型以保持API一致性）
   * 
   * @example
   * const timerId = timerManager.addTimer(1000, () => {
   *   console.log('定时器触发');
   * });
   */
  addTimer(timeout: number, callback: () => void): number {
    const timerId = this.__setTimeout(() => {
      // 定时器触发后，从活跃集合中移除ID
      this.__activeTimers.delete(timerId);
      callback();
    }, timeout);

    // 无论环境如何，都将定时器ID添加到活跃集合中进行跟踪
    this.__activeTimers.add(timerId);

    // 统一返回类型断言为number的ID，以保持API一致性
    return (isBrowser ? timerId : timerId as unknown) as number;
  }

  /**
   * 移除一个计时器
   * 
   * 封装了原生clearTimeout函数，并确保从活跃定时器集合中移除对应的ID。
   * 
   * @param timerId 要移除的计时器ID
   * 
   * @example
   * const timerId = timerManager.setTimeout(1000, () => {
   *   console.log('这个不会执行');
   * });
   * timerManager.removeTimer(timerId);
   */
  removeTimer(timerId: TimerId): void {
    this.__clearTimeout(timerId);
    // 无论ID类型如何，都从活跃集合中移除
    this.__activeTimers.delete(timerId);
  }

  /**
   * 移除所有活跃的计时器
   * 
   * 清除所有通过setTimeout方法创建且仍在活跃状态的定时器，
   * 并清空活跃定时器集合。
   * 
   * @example
   * timerManager.setTimeout(1000, () => console.log('定时器1'));
   * timerManager.setTimeout(2000, () => console.log('定时器2'));
   * timerManager.clearAll(); // 清除所有定时器
   */
  clearAll(): void {
    // 清除所有活跃的定时器
    this.__activeTimers.forEach(timerId => {
      this.__clearTimeout(timerId);
    });
    // 清空活跃定时器集合
    this.__activeTimers.clear();
  }

  /**
   * 添加一个带引用管理的计时器
   * 
   * 创建一个带名称标识的定时器，实例内部自动管理引用。
   * 如该名称的定时器已存在，会先清除旧的定时器再创建新的。
   * 
   * @param index 定时器的唯一标识名称
   * @param timeout 延迟时间（毫秒）
   * @param callback 延迟后执行的回调函数
   * 
   * @example
   * timerManager.addNamedTimer('myTimer', 1000, () => {
   *   console.log('命名定时器触发');
   * });
   */
  addNamedTimer(
    index: string,
    timeout: number,
    callback: () => void
  ): void {
    // 先清除同名的定时器（如果存在）
    this.clearNamedTimeout(index);
    // 创建新的定时器并存储在内部引用中
    this.__namedTimeouts[index] = this.addTimer(timeout, callback);
  }

  /**
   * 清除一个带引用管理的计时器
   * 
   * 根据指定的名称，清除对应的命名定时器并从内部引用中移除。
   * 
   * @param index 要清除的定时器唯一标识名称
   * 
   * @example
   * timerManager.setNamedTimeout('myTimer', 1000, () => {
   *   console.log('这个不会执行');
   * });
   * timerManager.clearNamedTimeout('myTimer');
   */
  clearNamedTimeout(
    index: string
  ): void {
    if (this.__namedTimeouts[index]) {
      // 清除定时器
      this.removeTimer(this.__namedTimeouts[index]!);
      // 从内部引用对象中移除
      this.__namedTimeouts[index] = undefined;
    }
  }

  /**
   * 清除所有带引用管理的计时器
   * 
   * 清除当前实例内部管理的所有命名定时器。
   * 
   * @example
   * timerManager.setNamedTimeout('timer1', 1000, () => console.log('1'));
   * timerManager.setNamedTimeout('timer2', 2000, () => console.log('2'));
   * timerManager.clearAllNamedTimeouts(); // 清除所有命名定时器
   */
  clearAllNamedTimeouts(): void {
    // 遍历内部引用对象中的所有定时器并清除
    Object.keys(this.__namedTimeouts).forEach(index => {
      this.clearNamedTimeout(index);
    });
  }
}

/**
 * 创建定时器管理器工厂函数
 * 
 * 创建并返回一个新的TimerManager实例。
 * 当需要多个独立的定时器管理器实例时使用。
 * 
 * @returns 新的定时器管理器实例
 * 
 * @example
 * const myTimerManager = createTimerManager();
 */
export function createTimerManager(): TimerManager {
  return new TimerManager();
}

/**
 * 全局定时器管理器实例
 * 
 * 提供了一个默认的全局TimerManager实例，适用于大多数场景。
 * 推荐在单应用中使用此实例以集中管理所有定时器。
 * 
 * @example
 * // 直接使用全局实例
 * timerManager.setTimeout(1000, () => {
 *   console.log('使用全局定时器管理器');
 * });
 */
export const timerManager = createTimerManager();